package v1

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"path"
	"strconv"
	"strings"
	"time"
	"web_framework/config"
	"web_framework/model"
	"web_framework/pkg/constvar"
	. "web_framework/pkg/e"
	"web_framework/pkg/errno"
	"web_framework/router/api"
	"web_framework/router/middleware"
	"web_framework/service"
	"web_framework/storage"
	"web_framework/websocket/v1/handler"
)

type StorageAPI struct {
	Uid         int    `db:"uid" json:"uid"`
	NickName    string `db:"nickname" json:"nickname,omitempty"`
	PhoneArea   string `db:"phone_area" json:"phone_area,omitempty"`
	PhoneNumber string `db:"phone_number" json:"phone_number,omitempty"`
	Password    string `db:"password" json:"password"`
}

func (a StorageAPI) Name() string {
	return "user API"
}

func (a StorageAPI) Bind() {
	storageAPIValid := config.C.Router.Group("/api/v1/data", middleware.CheckVer(), middleware.TrackLog())
	{
		storageAPIValid.GET("storage/scan", scan)
		storageAPIValid.GET("storage/list_files", listFiles)
		storageAPIValid.GET("storage/delete_file", deleteFile)
		storageAPIValid.GET("list_devs", listDevs)

	}

	storageAPI := config.C.Router.Group("/api/v1/return", middleware.TrackLog())
	{
		storageAPI.POST("scan_return", scanReturn)
		storageAPI.POST("search_return", SearchReturn)
	}
}

var checkStorageScan = []string{"sn", "uid", "token", "path"}
//2.3.1 发送扫描命令
func scan(c *gin.Context) {
	var hasTask int = 0

	type Data struct {
		HasTask int `json:"hasTask"`
	}
	// 检查参数是否为空
	err, params := CheckEmptyParams(c, checkStorageScan)
	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}

	//查询当前是否有下载中的任务
	loadingTaskQueueCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterTaskLoadIngSetKey
	laodTaskNum, loadingErr := storage.RedisZCard(loadingTaskQueueCacheKey)
	if loadingErr != nil {
		Log("Err", "获取队列任务数失败")
		api.SendResponse(c, errno.ErrPushStorageScan, nil)
	}
	if laodTaskNum > 0 {
		hasTask = 1
	}
	httpData := Data{
		HasTask: hasTask,
	}
	//查询缓存是否过期
	storagePathInfoExpireCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisStorageFileInfoExpireKey + ":" + params["path"]
	if storage.RedisExists(storagePathInfoExpireCacheKey) {
		api.SendResponse(c, errno.OK, httpData)
		return
	}

	storageFileCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterStorageFileKey + ":" + params["path"]
	pathQueueCache, getCacheError := storage.RedisZRange(storageFileCacheKey, 0, -1)
	if getCacheError != nil {
		api.SendResponse(c, getCacheError, nil)
		return
	}
	//删除目录缓存详情
	for _, path := range pathQueueCache {
		pathInfoCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisStorageFileInfoKey + ":" + path
		storage.RedisDelete(pathInfoCacheKey)
	}
	//删除目录队列信息
	storage.RedisDelete(storageFileCacheKey)
	//从路由器目录缓存集合中移除
	storagePathAllCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterStorageFileKey + ":all"
	storage.RedisZRem(storagePathAllCacheKey, params["path"])

	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}

	mid, getMidErr := service.GenMid(params["sn"])
	if getMidErr != nil {
		api.SendResponse(c, getMidErr, nil)
		return
	}

	var returnData interface{} = model.ResponseData{
		Sn:   params["sn"],
		Path: params["path"],
	}

	requestMsg := model.Response{
		Mid:   mid,
		Mtype: constvar.MtypeRouterStorageScanRequest,
		Time:  time.Now().Unix(),
		Data:  &returnData,
	}
	client := []string{params["sn"]}
	errSend := handler.WriteMessage(client, requestMsg)
	if errSend != nil {
		api.SendResponse(c, errno.ErrPushStorageScan, nil)
		return
	}

	api.SendResponse(c, errno.OK, httpData)
	return
}

//2.3.4	分页获取文件列表
func listFiles(c *gin.Context) {
	var checkParams = []string{"sn", "uid", "token", "path", "sver", "pageNo"}
	// 检查参数是否为空
	err, params := CheckEmptyParams(c, checkParams)
	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}
	type responseData struct {
		Path  string       `json:"path,omitempty"`
		Files []model.File `json:"files"`
	}
	var returnData = responseData{
		Path: params["path"],
	}

	for a := 0; a < 5; a++ {
		time.Sleep(time.Duration(1) * time.Second)

		storagePathAllCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterStorageFileKey + ":all"
		if ok,_ := storage.RedisZIsMember(storagePathAllCacheKey , params["path"]);!ok{
			Log("Info", "未查到目录缓存")
			continue
		}

		storagePathCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterStorageFileKey + ":" + params["path"]
		pageNumInt, err := strconv.Atoi(params["pageNo"])
		sliceFile, err := service.GetStoragePathQueueInfoCache(storagePathCacheKey, params["sn"], pageNumInt)
		if err != nil{
			Log("Info", "读取目录缓存失败")
		}else if sliceFile == nil{
			sliceFile = make([]model.File, 0)
		}

		returnData.Files = sliceFile

		errno.OK.Info = "获取成功"
		api.SendResponse(c, errno.OK, returnData)
		return
	}

	returnData.Files = make([]model.File, 0)

	api.SendResponse(c, errno.ErrGetStoragePathInfo, returnData)
	return
}

type scanReturnRequest struct {
	Time int             `json:"time,omitempty"`
	Data *scanReturnData `json:"data,omitempty"`
}

type scanReturnData struct {
	Sver   int           `json:"sver,omitempty"`
	Path   string        `json:"path,omitempty"`
	Hver   string        `json:"hver,omitempty"`
	Sn     string        `json:"sn,omitempty"`
	Mac    string        `json:"mac,omitempty"`
	Done   int           `json:"Done,omitempty"`
	Sign   string        `json:"sign,omitempty"`
	PageNo int           `json:"pageNo,omitempty"`
	Files  *[]model.File `json:"files"`
}

var checkScanReturn = []string{"sver", "hver", "sn", "uid", "token", "path", "mac", "done", "sign", "pageNo", "files"}
var emptyScanReturnCheck []string
//2.3.3	下载模块回调扫描结果
func scanReturn(c *gin.Context) {
	var data scanReturnRequest
	c.BindJSON(&data)
	//检查软件版本号
	err := CheckVersion(strconv.Itoa(data.Data.Sver))
	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}
	// 校验签名
	p4pclient4k := config.C.P4pclientSk
	paramsSign := []string{p4pclient4k, strconv.Itoa(data.Time), data.Data.Sn, data.Data.Mac}
	err = CheckSign(data.Data.Sign, paramsSign)
	if err != nil {
		api.SendResponse(c, errno.SignError, nil)
		return
	}
	if data.Data.Files != nil {
		for index, originFile := range *data.Data.Files {
			var newFile model.ResponseFile
			newFile.Path = data.Data.Path + "/" +originFile.Path
			newFile.LastModified = TimeToStr(originFile.LastModified)
			filenameWithSuffix := path.Base(originFile.Path)
			if originFile.Size == 0 {
				newFile.Type = model.StoragePathTypeDirectory
				newFile.Name = filenameWithSuffix
			} else {
				fileSuffix := path.Ext(filenameWithSuffix)
				filename := strings.TrimSuffix(filenameWithSuffix, fileSuffix)
				newFile.Name = filename
				if fileType, ok := config.StorageFileType[fileSuffix]; ok {
					newFile.Type = fileType
				} else {
					newFile.Type = model.StoragePathTypeOther
				}
				newFile.Size = originFile.Size
			}
			//添加文件详细信息缓存
			filesJson, _ := json.Marshal(newFile)
			storagePathInfoCacheKey := constvar.RouterRedisPrefix + data.Data.Sn + storage.RedisStorageFileInfoKey + ":" + newFile.Path

			_, setErr := storage.RedisSet(storagePathInfoCacheKey, filesJson, -1)
			if setErr != nil {
				api.SendResponse(c, setErr, nil)
				return
			}
			//另起一个缓存记录缓存有效时间，分页的缓存永久有效，重新扫描时更新
			storagePathInfoExpireCacheKey := constvar.RouterRedisPrefix + data.Data.Sn + storage.RedisStorageFileInfoExpireKey + ":" + newFile.Path

			_, setExpireErr := storage.RedisSet(storagePathInfoExpireCacheKey, 1, 600)
			if setExpireErr != nil {
				api.SendResponse(c, setExpireErr, nil)
				return
			}
			//添加到有序集合
			sort := int64(index + 1 + (data.Data.PageNo-1)*10)
			storagePathCacheKey := constvar.RouterRedisPrefix + data.Data.Sn + storage.RedisRouterStorageFileKey + ":" + data.Data.Path
			_, err := storage.RedisZAdd(storagePathCacheKey, newFile.Path, sort)
			if err != nil {
				api.SendResponse(c, err, nil)
				return
			}
		}
		//记录所有该路由器下所有的已读取过的缓存目录
		storagePathAllCacheKey := constvar.RouterRedisPrefix + data.Data.Sn + storage.RedisRouterStorageFileKey + ":all"
		_, err := storage.RedisZAdd(storagePathAllCacheKey, data.Data.Path, 1)
		if err != nil {
			api.SendResponse(c, err, nil)
			return
		}
	}

	api.SendResponse(c, errno.OK, nil)
	return
}

//2.3.5删除文件
func deleteFile(c *gin.Context) {
	var checkParams = []string{"sn", "uid", "token", "path", "sver"}
	// 检查参数是否为空
	err, params := CheckEmptyParams(c, checkParams)
	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}
	//推送到下载模块
	mid, getMidErr := service.GenMid(params["sn"])
	if getMidErr != nil {
		api.SendResponse(c, getMidErr, nil)
		return
	}

	var returnData interface{} = model.ResponseData{
		Sn:   params["sn"],
		Path: params["path"],
	}

	requestMsg := model.Response{
		Mid:   mid,
		Mtype: constvar.MtypeRouterDelPathRequest,
		Time:  time.Now().Unix(),
		Data:  &returnData,
	}
	client := []string{params["sn"]}
	errSend := handler.WriteMessage(client, requestMsg)
	if errSend != nil {
		api.SendResponse(c, errno.ErrDelStoragePathI, nil)
		return
	}
	deleteFileMidCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisDelPathMidCacheKey + ":" + strconv.Itoa(mid)
	if bool, _ := storage.RedisSet(deleteFileMidCacheKey, 1, 20); bool == false {
		Log("Info", "删除文件缓存标记失败")
	}
	parentPath := path.Dir(path.Dir(params["path"]))
	for a := 0; a < 5; a++ {
		time.Sleep(time.Duration(2) * time.Second)
		fileCache, err := storage.RedisGet(deleteFileMidCacheKey)
		if err != nil || fileCache == "1" {
			Log("Info", "读取删除文件缓存标记")
			continue
		} else {
			//删除详情
			storagePathInfoCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisStorageFileInfoKey + ":" + params["path"]
			storage.RedisDelete(storagePathInfoCacheKey)
			//从父类目录有序集合中移除该path
			storagePathCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisRouterStorageFileKey + ":" + parentPath
			storage.RedisZRem(storagePathCacheKey, params["path"])
		}

		errno.OK.Info = "删除成功"
		api.SendResponse(c, errno.OK, returnData)
		return
	}

	api.SendResponse(c, errno.ErrDelStoragePathI, nil)
	return
}

// 首页 展示分区列表和默认分区数据
func listDevs(c *gin.Context) {
	var checkParams = []string{"sn", "uid", "token", "sver"}
	// 检查参数是否为空
	err, params := CheckEmptyParams(c, checkParams)
	if err != nil {
		api.SendResponse(c, err, nil)
		return
	}

	sn := params["sn"]
	// 先查询是否存在默认分区数据
	dPathKey, err := service.GetDefaultPath(sn)
	partitionCacheKey := constvar.RouterRedisPrefix + params["sn"] + storage.RedisStoragePartitionInfoKey
	if dPathKey != "" && err == nil {

		// 查询缓存是否存在
		var storagePartition map[string]model.ResponsePartition
		if storage.RedisExists(partitionCacheKey) {
			partitions, cacheErr := storage.RedisGet(partitionCacheKey)
			if cacheErr != nil {
				Log("Info", "读取分区缓存失败")
				api.SendResponse(c, cacheErr, nil)
				return
			}
			json.Unmarshal([]byte(partitions), &storagePartition)
			returnData := make([]model.ResponsePartition, 0)
			for _, partition := range storagePartition {
				partition.Index = 0
				partition.IsDPath = 0
				checkDPathKey := Md5Str(partition.Sn + partition.UUID + strconv.Itoa(partition.Seq))
				if checkDPathKey == dPathKey {
					partition.IsDPath = 1
				}
				returnData = append(returnData, partition)
			}
			errno.OK.Info = ""
			api.SendResponse(c, errno.OK, returnData)
			return
		}
	}

	// 推送到下载模块
	mid, getMidErr := service.GenMid(params["sn"])
	if getMidErr != nil {
		api.SendResponse(c, getMidErr, nil)
		return
	}

	requestMsg := model.Response{
		Mid:   mid,
		Mtype: constvar.MTypeGetStorageParRequest,
		Time:  time.Now().Unix(),
		Data:  nil,
	}
	client := []string{params["sn"]}
	errSend := handler.WriteMessage(client, requestMsg)
	if errSend != nil {
		api.SendResponse(c, errno.ErrGetStoragePar, nil)
		return
	}
	for a := 0; a < 5; a++ {
		time.Sleep(time.Duration(2) * time.Second)
		partitions, cacheErr := storage.RedisGet(partitionCacheKey)
		if cacheErr != nil {
			Log("Info", "读取分区缓存失败")
			continue
		}
		dPathKey, err = service.GetDefaultPath(sn)
		if err != nil {
			Log("Info", "读取默认分区下载模块缓存失败")
			continue
		}
		// 设置默认下载分区
		var storagePartition map[string]model.ResponsePartition
		json.Unmarshal([]byte(partitions), &storagePartition)

		returnData := make([]model.ResponsePartition, 0)
		for _, partition := range storagePartition {
			partition.IsDPath = 0
			checkDPathKey := Md5Str(partition.Sn + partition.UUID + strconv.Itoa(partition.Seq))
			if checkDPathKey == dPathKey {
				partition.IsDPath = 1
			}
			returnData = append(returnData, partition)
		}
		errno.OK.Info = ""
		api.SendResponse(c, errno.OK, returnData)
		return
	}

	api.SendResponse(c, errno.ErrGetStoragePar, nil)
	return
}

// 3.3 下载模块回调
func SearchReturn(c *gin.Context) {

	type requestSt struct {
		Time int             `json:"time,omitempty"`
		Data *model.SearchRequestData `json:"data,omitempty"`
	}
	var request = requestSt{}
	c.BindJSON(&request)
	var requestData = request.Data

	t := strconv.Itoa(request.Time)
	paramsSign := []string{config.C.P4pclientSk,t,requestData.Sn,requestData.Mac}
	err := CheckSign(requestData.Sign, paramsSign)
	if err != nil {
		api.SendResponse(c, errno.SignError, nil)
		return
	}

	// 将返回的数据返回给搜索的redis
	var len int
	cacheLenKey := constvar.RouterRedisPrefix + requestData.Sn + constvar.RedisSearchLocalFilesPageCacheKey
	if storage.RedisExists(cacheLenKey) {
		len,_ = storage.RedisIncr(cacheLenKey)
	}else {
		len,_ = storage.RedisIncr(cacheLenKey)
		storage.RedisExpire(cacheLenKey,  constvar.TimeFormatSecond * 10) // 初次给队列设置为5秒钟过期
	}

	var searchDataRe []model.ResourceSearch
	cacheKey := constvar.RouterRedisPrefix + requestData.Sn + constvar.RedisSearchLocalFilesCacheKey + ":flies:" + Md5Str(requestData.Keyword) + ":" + strconv.Itoa(len)
	if !storage.RedisExists(cacheKey) {
		for _, v:= range requestData.Files {
			if v.IsPath == 0 {
				var searchData model.ResourceSearch
				searchData.Path = v.Path
				searchData.Name = GetUrlName(v.Path)
				searchDataRe = append(searchDataRe, searchData)
			}
		}
		da, _ := json.Marshal(searchDataRe)
		Log("Warn", "[Key] ", cacheKey)
		Log("Warn", "[Data] ", da)
		storage.RedisSet(cacheKey, da, constvar.RedisExpireMinute5)
	}

	cacheDoneKey := constvar.RouterRedisPrefix + requestData.Sn + constvar.RedisSearchLocalFilesDoneCacheKey
	storage.RedisSet(cacheDoneKey, requestData.Done, 10 * constvar.TimeFormatSecond)

	errno.OK.Info = ""
	api.SendResponse(c, errno.OK, nil)
	return
}
